# koroFileHeader at Yungoal acer
# Create: 2021-06-09 12:30:47
# LastEdit: 2021-06-09 17:56:04
"""替代 Env().read_env 这个静态方法
实现对加密信息的解密
"""
__author__ = '749B'

import inspect
from io import StringIO
import os
import typing
from pathlib import Path

from dotenv.main import _walk_to_root, find_dotenv, DotEnv
from .vault import decrypt

_Value = typing.Union[str, int, None]


def read_env(
    path: typing.Optional[str] = None,
    recurse: bool = True,
    verbose: bool = False,
    override: bool = False,
    password: typing.Optional[str] = None,
) -> bool:
    """Read a .env file into os.environ.

    If .env is not found in the directory from which this method is called,
    the default behavior is to recurse up the directory tree until a .env
    file is found. If you do not wish to recurse up the tree, you may pass
    False as a second positional argument.

    如果遇到加密信息，密码错误，解密失败，这里会返回False。
    调用时判断返回值来确认是否成功加载了文件中的环境变量。
    """
    if path is None:
        # By default, start search from the same directory this function is called
        current_frame = inspect.currentframe()
        if current_frame is None:
            raise RuntimeError("Could not get current call frame.")
        frame = current_frame.f_back
        assert frame is not None
        caller_dir = Path(frame.f_code.co_filename).parent.resolve()
        start = caller_dir / ".env"
    else:
        if Path(path).is_dir():
            raise ValueError("path must be a filename, not a directory.")
        start = Path(path)
    if recurse:
        start_dir, env_name = os.path.split(start)
        if not start_dir:  # Only a filename was given
            start_dir = os.getcwd()
        for dirname in _walk_to_root(start_dir):
            check_path = Path(dirname) / env_name
            if check_path.exists():
                return load_dotenv(check_path, verbose=verbose, override=override, password=password)

    else:
        return load_dotenv(str(start), verbose=verbose, override=override, password=password)

def load_dotenv(
    dotenv_path: typing.Union[typing.Text, os.PathLike, None] = None,
    stream: typing.Optional[StringIO] = None,
    verbose: bool = False,
    override: bool = False,
    interpolate: bool = True,
    encoding: typing.Optional[typing.Text] = "utf-8",
    password: typing.Optional[str] = None,
) -> bool:
    """Parse a .env file and then load all the variables found as environment variables.

    - *dotenv_path*: absolute or relative path to .env file.
    - *stream*: `StringIO` object with .env content, used if `dotenv_path` is `None`.
    - *verbose*: whether to output a warning the .env file is missing. Defaults to
      `False`.
    - *override*: whether to override the system environment variables with the variables
      in `.env` file.  Defaults to `False`.
    - *encoding*: encoding to be used to read the file.

    If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file.
    """
    f = dotenv_path or stream or find_dotenv()
    dotenv = MyDotEnv(
        f,
        verbose=verbose,
        interpolate=interpolate,
        override=override,
        encoding=encoding,
    )
    return dotenv.set_as_environment_variables(password)

class MyDotEnv(DotEnv):
    """修改一个 set_as_environment_variables 方法"""

    def set_as_environment_variables(self, password: typing.Optional[str] = None) -> bool:
        """
        Load the current dotenv as system environment variable.
        """
        if password is None:
            # decrypt 函数的 password 必须是字符串，把可能接收到的None转为空字符串
            password = ''
        for k, v in self.dict().items():
            if k in os.environ and not self.override:
                continue
            if v is not None:
                # 不考虑Python2的兼容问题了，所以不需要to_env函数来转成python2需要的bytes类型了
                #os.environ[to_env(k)] = to_env(v)
                v_ = decrypt(v, password)
                if v_ is None:
                    # 密码错误会导致获取到None
                    return False
                os.environ[k] = v_

        return True


def parse_envfile(envfile: str) -> typing.Tuple[str, typing.Optional[str]]:
    """解析envfile，返回envfile和密码
    envfile的文件扩展名是.env，在这之后的字符就是解密用的密码
    """
    name, ext = os.path.splitext(envfile)
    if ext.startswith('.env'):
        password = ext[4:]
        return '%s.env' % name, password
    return envfile, None
    

def init_environs(envfile: str) -> typing.Union[typing.Dict[str, _Value], bool]:
    """初始化：环境变量
    如果遇到加密信息，并且解密失败（密码错误），则返回False
    正常返回字典，就是返回之后需要在后续代码中使用的环境变量
    """
    from environs import Env
    env = Env()
    if envfile == '.env':
        res = read_env()
    else:
        path, password = parse_envfile(envfile)
        res = read_env(os.path.join('.env', path), recurse=False, password=password)
    if not res:
        return False
    return {
        'cloud_name': env.str('AZURE_CLOUD', 'AZURE_CHINA_CLOUD'),
        'runtime_env': env.str('RUNTIMEENV', 'notset'),
    }
